使用 QT6 实现截图功能 实现简介 改了一下 Qt项目中,实现屏幕截图功能的模块详细实现 的代码
截图区域的获取。监听鼠标点击,移动,释放事件,获取需要截图区域的左上角和右下角的坐标。
截图区域的绘制。使用 drawPixmap
绘制截图区域。
截图的保存。通过 qt 提供的方法保存文件即可。
其他,鼠标右键的选项菜单通过 menu 实现,使用槽机制添加各个选项的对应方法。
学习总结 在截图区域点击鼠标右键,会唤起菜单。主要实现是通过 contextMenuEvent
事件和 QMenu 实现。
主要代码
1 2 3 4 5 6 7 menu = new QMenu (this ); menu->addAction ("保存当前截图" , this , SLOT (saveScreen ())); menu->addAction ("保存全屏截图" , this , SLOT (saveFullScreen ())); menu->addAction ("截图另存为" , this , SLOT (saveScreenOther ())); menu->addAction ("全屏另存为" , this , SLOT (saveFullOther ())); menu->addAction ("退出截图" , this , SLOT (hide ()));
1 2 3 4 5 6 void ScreenWidget::contextMenuEvent (QContextMenuEvent *) { this ->setCursor (Qt::ArrowCursor); menu->exec (cursor ().pos ()); }
鼠标点击,释放,移动事件 通过重写 Widget 的方法来实现鼠标相关事件的监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 void ScreenWidget::mousePressEvent (QMouseEvent *e) { int status = screen->getStatus (); if (status == Screen::CLICK) { screen->setStart (e->pos ()); } else if (status == Screen::MOV) { if (screen->isInArea (e->pos ()) == false ) { screen->setStart (e->pos ()); screen->setStatus (Screen::CLICK); } else { movPos = e->pos (); this ->setCursor (Qt::SizeAllCursor); } } this ->update (); } void ScreenWidget::mouseMoveEvent (QMouseEvent *e) { int status = screen->getStatus (); if (status == Screen::CLICK) { screen->setEnd (e->pos ()); } else if (screen->getStatus () == Screen::MOV) { QPoint p (e->x () - movPos.x (), e->y () - movPos.y ()); screen->move (p); movPos = e->pos (); } this ->update (); } void ScreenWidget::mouseReleaseEvent (QMouseEvent *e) { if (screen->getStatus () == Screen::CLICK) { screen->setStatus (Screen::MOV); } else if (screen->getStatus () == Screen::MOV) { this ->setCursor (Qt::ArrowCursor); } this ->update (); }
通常会读取 e 对象中的 button 来判断是鼠标左边点击还是哪里点击。
这里使用了自己定义的状态,是因为截图时会点击鼠标并移动(同时触发点击与移动事件,就不能通过简单的判断事件类型去定义起始坐标),使用自己定义的状态可以更好的管理截图区域的坐标。
paintEvent, showEvent,drawPixmap 截图区域的绘制主要就是通过 QPainter 实现的。
showEvent
会在窗口显示时自动调用,在这个程序里就是实例被创建且调用后。(这也是为什么程序运行后全屏会变为灰色,是 showEvent 绘制出来的背景。
paintEvent
的首次调用是所有 ui 绘制完成后,然后就是 ui 发生变化时触发。在这个程序中,当鼠标点击,鼠标移动以及鼠标释放后,都会执行一句 this->update()
,更新 ui,随后触发 paintEvent,在 paintEvent 这个回调函数中通过 drawPixmap 配合坐标和缩放因子绘制截图区域。
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 void ScreenWidget::paintEvent (QPaintEvent *) { int x = screen->getLeftUp ().x (); int y = screen->getLeftUp ().y (); int w = screen->getRightDown ().x () - x; int h = screen->getRightDown ().y () - y; QPainter painter (this ) ; QPen pen; pen.setColor (Qt::green); pen.setWidth (2 ); pen.setStyle (Qt::DotLine); painter.setPen (pen); painter.drawPixmap (0 , 0 , *bgScreen); if (w > 0 && h > 0 ) { QRect sourceRect (x * pixelRatio, y * pixelRatio, w * pixelRatio, h * pixelRatio) ; QRect targetRect (x, y, w, h) ; painter.drawPixmap (targetRect, *fullScreen, sourceRect); } painter.drawRect (x, y, w, h); pen.setColor (Qt::black); painter.setPen (pen); painter.drawText (x + w + 2 , y + 12 , tr ("%5 x %6" ) .arg (w * pixelRatio) .arg (h * pixelRatio)); } void ScreenWidget::showEvent (QShowEvent *) { QPoint point (-1 , -1 ) ; screen->setStart (point); screen->setEnd (point); QScreen *pscreen = QApplication::primaryScreen (); *fullScreen = pscreen->grabWindow (0 , 0 , 0 , screen->width (), screen->height ()); printf ("width: %d, height: %d" , fullScreen->width (), fullScreen->height ()); QPixmap pix (screen->width(), screen->height()) ; pix.fill ((QColor (160 , 160 , 160 , 200 ))); bgScreen = new QPixmap (*fullScreen); QPainter p (bgScreen) ; p.drawPixmap (0 , 0 , pix); }
遇到的问题 源代码中使用 QDesktopWidget 获取屏幕尺寸,QT6 开始不支持这个类了。可以使用 QGuiApplication 代替。通过 QGuiApplication::primaryScreen()
获取。
源代码在本机测试,截图时截图区域会放大,并且边框与图片区域有 padding 原因:在高 DPI 显示器上,系统会对屏幕内容进行缩放(例如 150% 或 200%)。在抓取屏幕时,如果没有考虑高 DPI 缩放因子,就会导致捕获图像在显示时出现放大的情况。
解决方案:使用 QApplication::primaryScreen()->devicePixelRatio()
获取缩放因子,在截图区域绘制时,绘制的坐标都与缩放因子相乘,就可以得到正确的绘制区域。伪代码如下:
1 2 3 4 5 6 7 8 9 qreal pixelRatio = QApplication::primaryScreen ()->devicePixelRatio (); if (w > 0 && h > 0 ) { QRect sourceRect (x * pixelRatio, y * pixelRatio, w * pixelRatio, h * pixelRatio) ; QRect targetRect (x, y, w, h) ; painter.drawPixmap (targetRect, *fullScreen, sourceRect); }
不仅在绘制截图区域的时候需要考虑缩放因子,在截图区域保存时也需要考虑这个问题。
下一步计划
添加开始界面
可以反复截图
出现截图菜单,可以复制到剪切板
实现常见截图软件应该有的功能